﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WordBinder.Binder
{
    public static class Extensions
    {
        public static object SelectSingle(this object data, string path)
        {
            // recurse to get the root value
            var delimPos = path.IndexOf('.');
            string nextprop;

            if (string.IsNullOrEmpty(path))
            {
                return data;
            }
            else if (delimPos == -1)
            {
                nextprop = path;
                path = string.Empty;
            }
            else
            {
                nextprop = path.Substring(0, delimPos);
                path = path.Substring(delimPos + 1);
            }

            var filter = default(string[]);
            if (nextprop.EndsWith("]"))
            {
                delimPos = nextprop.LastIndexOf('[');
                filter = nextprop.Substring(delimPos + 1).TrimEnd(']').Split('=');
                nextprop = nextprop.Substring(0, delimPos);
            }

            var prop = data.GetType().GetProperty(nextprop, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            if (filter != null)
            {
                var value = prop.GetValue(data, null);
                // if enumerable (not a string) filter each item
                foreach (var listItem in value is IEnumerable && !(value is string) ? (IEnumerable)value : new[] { value })
                {
                    // recurse to get value to check
                    var filterValue = listItem.SelectSingle(filter[0]);
                    // only checks equality at the mo
                    var fixtype = filterValue.FixType(filter[1]);
                    if (fixtype != null && Equals(filterValue, fixtype))
                    {
                        return listItem.SelectSingle(path);
                    }
                }
                return null;
            }
            else
            {
                var val = prop.GetValue(data, null);
                if (val != null)
                    return val.SelectSingle(path);
                else
                    return null;
            }
        }

        public static object FixType(this object value, string filtervalue)
        {
            var typeConverter = TypeDescriptor.GetConverter(value.GetType());
            if (typeConverter != null && typeConverter.CanConvertFrom(filtervalue.GetType()))
            {
                return typeConverter.ConvertFrom(filtervalue);
            }

            return null;
        }

        public static IEnumerable<object> Query(this object data, string path)
        {
            var delimPos = path.LastIndexOf('.');
            IEnumerable<object> root;
            if (delimPos == -1)
            {
                root = new[] { data };
            }
            else
            {
                root = Query(data, path.Substring(0, delimPos));
                path = path.Substring(delimPos + 1);
            }

            var filter = default(string[]);
            if (path.EndsWith("]"))
            {
                delimPos = path.LastIndexOf('[');
                filter = path.Substring(delimPos + 1).TrimEnd(']').Split('=');
                path = path.Substring(0, delimPos);
            }

            var prop = root.ElementAt(0).GetType().GetProperty(path, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            foreach (var rootItem in root)
            {
                var value = prop.GetValue(rootItem, null);

                if (value is IEnumerable && !(value is string))
                {
                    foreach (var rootItemItem in (IEnumerable)value)
                    {
                        if (filter != null)
                        {
                            var filterValue = rootItemItem.Query(filter[0]);
                            var element = filterValue.ElementAt(0);
                            var fixtype = element.FixType(filter[1]);
                            if (fixtype != null && Equals(element, fixtype))
                            {
                                yield return rootItemItem;
                            }
                        }
                        else
                        {
                            yield return rootItemItem;
                        }
                    }
                }
                else
                {
                    yield return value;
                }
            }
        }
    }
}
